package com.sxt.feign.core.fun.funImpl;

import com.sxt.feign.core.anno.HystixCmdRpc;
import com.sxt.feign.core.enums.HystrixStatus;
import com.sxt.feign.core.fun.HystixCmd;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 熔断器的功能实现
 * 窗口滑动机制
 * @author LSX
 * @create 2019/8/22
 */
public class HystixCmdImpl implements HystixCmd {
    // 定义一次窗口滑动的时间为5秒
    private static long WINDOW_SLIDE_TIME = 5000L;

    // 定义一个窗口内失败的最大次数为10次
    private static Integer MAX_FAIL_COUNT = 10;

    // 定义熔断器初始状态为关闭状态
    private HystrixStatus status = HystrixStatus.CLOSE;

    // 初始化计数器（当前窗口内失败的次数）
    private AtomicInteger currentFallCount = new AtomicInteger(0);

    // 定义Object类型的锁
    private Object lock = new Object();

    // 实例随机数对象
    private static Random RDM = new Random();

    // 初始化一个进程的定时任务
    {
        new Thread(()->{
            while (true){
                try {
                    Thread.sleep(WINDOW_SLIDE_TIME);// 5s 执行一次检查
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(this.getStatus()==HystrixStatus.CLOSE){//熔断器关闭，服务正常
                    // 将当前失败次数设置为0
                    currentFallCount.set(0);
                }else {//熔断器打开或半开
                    System.out.println("断路器已经打开，我统计失败次数，没有任何意义，我先死一会");
                    synchronized (lock){
                        try {
                            lock.wait();
                            System.out.println("远程服务正常了，我需要在启动清空窗口数据了");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
    }

    /**
     * 拦截请求
     * @param point
     * @return
     * 进入该方法表示，调用服务失败，需要调用备胎服务
     */
    @Override
    public Object interceptor(ProceedingJoinPoint point) {
        // 获取签名，并去并强转为方法签名
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        // 通过方法签名得到方法（该方法是服务实现类方法）,我们需要调用备胎里面的方法
        Method method = methodSignature.getMethod();
        // 得到回调方法的对象，用于构造回调方法
        Object object = getFallCallback(method);
        // 得到备胎方法
        Method callBack = null ;
        try {
            callBack = object.getClass().getMethod(method.getName(),method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        // 调用备胎方法，得到备胎结果
        Object fallBackResult = null ;
        try {
            fallBackResult =  callBack.invoke(object,point.getArgs());
        } catch (IllegalAccessException  | InvocationTargetException e) {
            e.printStackTrace();
        }
        return fallBackResult;
    }

    /**
     *通过实现类方法得到，注解对应类型的回调方法的回调对象
     * @param method
     * @return
     */
    private Object getFallCallback(Method method) {
        HystixCmdRpc annotation = method.getAnnotation(HystixCmdRpc.class);
        Class<?> callback = annotation.callback();
        try {
            return callback.newInstance();
        } catch (InstantiationException | IllegalAccessException e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 进入该方法表示服务正常调用
     * @param point
     * @return
     */
    @Override
    public Object pass(ProceedingJoinPoint point) {
        Object result = null;
        try {
            result = point.proceed(point.getArgs());
        } catch (Throwable throwable) { // 进入catch表示出现异常，服务调用失败
            // 统计失败次数
            currentFallCount.getAndIncrement();
            // 判断失败次数是否超出阈值
            if(currentFallCount.get()>MAX_FAIL_COUNT){
                System.out.println("当前失败次数为："+currentFallCount.get()+",已经超出阈值，熔断器打开！");
                this.changeStatus(HystrixStatus.OPEN);
                // 熔断器打开一段时间，需要设置为半开状态（检查服务是否正常）
                new Thread(()->{
                    try {
                        Thread.sleep(5000L);
                        System.out.println("已经等待5秒，熔断器将进入半开状态！");
                        this.changeStatus(HystrixStatus.HALF_OPEN);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
            // 失败了，我们也不直接报错，而是返回备胎的结果
            result = interceptor(point);
        }
        return result;
    }

    /**
     * 检查服务是否上线，允许少许流量通过
     * @param point
     * @return
     */
    @Override
    public Object check(ProceedingJoinPoint point) {
        // 允许3%流量通过（100次重试访问服务三次）
        int num = RDM.nextInt(100) + 1;
        Object result =null;
        if(num<=3){
            try {
                result = point.proceed(point.getArgs());
                // 测试通过了
                this.changeStatus(HystrixStatus.CLOSE);
                // 开启窗口滑动来清空计数
                synchronized (lock) {
                    System.out.println("远程调用的测试已经通过，远程服务正常");
                    lock.notifyAll();
                }
            } catch (Throwable throwable) {
                // 没有通过重试机制
                System.out.println("测试没有通过");
                return interceptor(point);
            }
        }else {
            return interceptor(point);
        }
        return result;
    }

    @Override
    public void changeStatus(HystrixStatus status) {
        this.status = status;
    }

    @Override
    public HystrixStatus getStatus() {
        return this.status;
    }
}

